/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package nl.unimaas.games.tron.player.mcts;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Stack;
import nl.unimaas.games.tron.engine.Board;

/**
 *
 * @author Cliff
 */
public class Node implements Serializable {

    public Node parent;
    public final ArrayList<Node> children = new ArrayList<Node>(3);
    //the move that was performed to get to this state
    public final int move;
    public int playerPosition = -1;
    public final int playerNr;
    public final boolean max;
    public boolean expanded = false;
    public double value = 0;
    public int visits = 0;
    public int wins = 0, draws = 0;
    public long id = 0;
    public int depth = 0;
    public Object tag;
    public double spaceDiff = Double.NaN;
    public boolean connected = true;
    public int prediction = CutOffStrategy.UNDECIDED;
    public static long lastId = 0;

    public Node(int playerNr, boolean max) {
        this.parent = null;
        this.move = Board.MOVE_NONE;
        this.depth = -1; // -1 since it has no move, and ngramselection needs the depth in this format
        this.playerNr = playerNr;
        this.max = max;
        id = lastId;
        lastId++;
    }

    public Node(int playerNr, boolean max, Board board) {
        this.parent = null;
        this.depth = -1; // -1 since it has no move, and ngramselection needs the depth in this format
        this.move = Board.MOVE_NONE;
        this.playerNr = playerNr;
        
        int pos = board.getPlayerPosition(this.playerNr);
        int x = Board.posToX(pos), y = Board.posToY(pos);
        this.playerPosition = Board.getPositionFromMove(x, y, this.move);
        
        this.max = max;
        id = lastId;
        lastId++;
    }

    public Node(Node parent, int playerNr, int move, boolean max, Board board) {
        this.parent = parent;
        this.depth = parent.depth + 1;
        this.move = move;
        this.playerNr = playerNr;
        
        int pos = board.getPlayerPosition(this.playerNr);
        int x = Board.posToX(pos), y = Board.posToY(pos);
        this.playerPosition = Board.getPositionFromMove(x, y, this.move);
        
        this.max = max;
        id = lastId;
        lastId++;
    }

    public boolean isRoot() {
        return (parent == null);
    }

    public boolean isLeaf() {
        return (children.size() == 0);
    }

    public boolean isOpponentNode() {
        return (!max);
    }

    public void apply(Board board) {
        board.performMove(playerNr, move, false);
        this.playerPosition = board.getPlayerPosition(this.playerNr);
    }

    public void revert(Board board) {
        int undoMove = move * -1;
        board.performMove(playerNr, undoMove, false);
    }

    /** Convenience method, for debugging purposes */
    public void applyFromRoot(Board board) {
        Stack<Node> path = new Stack<Node>();
        Node current = parent;
        while (current.parent != null) {
            path.push(current);
            current = current.parent;
        }
        while (!path.isEmpty()) {
            current = path.pop();
            current.apply(board);
        }
    }

    public void revertToRoot(Board board) {
        revert(board);
        if (parent != null) {
            parent.revertToRoot(board);
        }
    }

    @Override
    public String toString() {
        return String.format("node[id=%d, move=%s, player=%d, visits=%d, wins=%.1f%% (%d), draws=%.1f%% (%d), value=%.2f, children=%d]", id, Board.moveToString(move), playerNr, visits, wins / (float) visits * 100, wins, draws / (float) visits * 100, draws, value, children.size());
    }

    public void printChildren() {
        for (int i = 0; i < children.size(); i++) {
            System.out.println("	" + children.get(i) + ", ");
        }
    }
}
